/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.json.jackson;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionLikeType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import net.apexes.commons.lang.Enume;
import net.apexes.commons.lang.EnumeRegistry;

import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public final class Jacksons {
    private Jacksons() {}

    public static final ObjectMapper defaultObjectMapper = createObjectMapper();

    private static ObjectMapper createObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }

    public static ObjectMapper forDefault(EnumeRegistry enumeRegistry) {
        return forDefault(enumeRegistry.getEnumeClasses());
    }

    public static ObjectMapper forDefault(Collection<Class<? extends Enume<?>>> enumeClasses) {
        defaultObjectMapper.registerModule(createModule(enumeClasses));
        return defaultObjectMapper;
    }
    
    public static SimpleModule createModule(EnumeRegistry enumeRegistry) {
        return createModule(enumeRegistry.getEnumeClasses());
    }
    
    public static SimpleModule createModule(Collection<Class<? extends Enume<?>>> enumeClasses) {
        return register(new SimpleModule(), enumeClasses);
    }
    
    public static SimpleModule register(SimpleModule module, EnumeRegistry enumeRegistry) {
        return register(module, enumeRegistry.getEnumeClasses());
    }
    
    public static SimpleModule register(SimpleModule module, Collection<Class<? extends Enume<?>>> enumeClasses) {
        for (Class<? extends Enume<?>> enumeClass : enumeClasses) {
            register(module, enumeClass);
        }
        return module;
    }
    
    public static SimpleModule register(SimpleModule module, Class<? extends Enume<?>> enumeClass) {
        Class<?> valueClass = Enume.valueClass(enumeClass);
        if (valueClass == String.class) {
            Class<Enume<String>> classType = (Class<Enume<String>>) enumeClass;
            module.addSerializer(new EnumeByStringJsonSerializer<>(classType));
            return module.addDeserializer(classType, new EnumeByStringJsonDeserializer<>(classType));
        }
        if (valueClass == Integer.class) {
            Class<Enume<Integer>> classType = (Class<Enume<Integer>>) enumeClass;
            module.addSerializer(new EnumeByIntegerJsonSerializer<>(classType));
            return module.addDeserializer(classType, new EnumeByIntegerJsonDeserializer<>(classType));
        }
        throw new IllegalArgumentException("value only be String or Integer.");
    }

    public static SimpleModule adaptiveDateDeserializer(SimpleModule module) {
        return module.addDeserializer(Date.class, AdaptiveDateJsonDeserializer.INSTANCE);
    }

    public static <D> JavaType constructParametricType(ObjectMapper objectMapper, Class<?> parametrized, Class<D> dataClass) {
        return objectMapper.getTypeFactory().constructParametricType(parametrized, dataClass);
    }

    public static <DE> JavaType constructListParametricType(ObjectMapper objectMapper, Class<?> parametrized, Class<DE> elementClass) {
        TypeFactory factory = objectMapper.getTypeFactory();
        CollectionLikeType likeType = factory.constructCollectionLikeType(List.class, elementClass);
        return factory.constructParametricType(parametrized, likeType);
    }
}
